/*
Copyright 2010 Farent-Solutions Ltd.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

 */
package com.farent.solutions.plugins.aribaweb.awl.completion;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JToolTip;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import org.netbeans.api.editor.completion.Completion;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.editor.BaseDocument;
import org.netbeans.modules.xml.axi.AXIComponent;
import org.netbeans.modules.xml.schema.completion.spi.CompletionContext;
import org.netbeans.spi.editor.completion.CompletionItem;
import org.netbeans.spi.editor.completion.CompletionResultSet;
import org.netbeans.spi.editor.completion.CompletionTask;
import org.netbeans.spi.editor.completion.support.AsyncCompletionQuery;
import org.netbeans.spi.editor.completion.support.AsyncCompletionTask;

/**
 *
 * @author farouka
 */
public abstract class AbstractCompletionItem1 implements CompletionItem {

    protected String itemText;
    protected TokenSequence tokenSequence;
    protected AwlCompletionContext context;
    protected AXIComponent axiComponent;
    protected String typedChars;
    protected int extraPaintGap = AwlCompletionPaintComponent.DEFAULT_ICON_WIDTH;
    protected javax.swing.Icon icon;
     protected boolean shift = false;
    private Logger log = Logger.getLogger(AbstractCompletionItem1.class.getName());

    public static final String
        ICON_ELEMENT    = "element.png",     //NOI18N
        ICON_ATTRIBUTE  = "attribute.png",   //NOI18N
        ICON_VALUE      = "value.png",       //NOI18N
        ICON_LOCATION   = "/org/farent/solutions/plugins/aribaweb/awl/completion/resources/";

    public AbstractCompletionItem1(AXIComponent component, CompletionContext context) {
        this(component, context, null);
    }

    public AbstractCompletionItem1(AXIComponent component, CompletionContext context,
        TokenSequence tokenSequence) {
        this.context = (AwlCompletionContext) context;
        this.axiComponent = component;
        this.tokenSequence = tokenSequence;
        if (context != null) {
            this.typedChars = context.getTypedChars();
        }
    }

    @Override
    public CharSequence getInsertPrefix() {
        return null;
    }

    @Override
    public boolean instantSubstitution(JTextComponent component) {
        return false;
    }

    @Override
    public int getSortPriority() {
        return 0;
    }

    public void setTokenSequence(TokenSequence tokenSequence) {
        this.tokenSequence = tokenSequence;
    }

    public AXIComponent getAXIComponent() {
        return axiComponent;
    }

    public abstract String getReplacementText();

    Color getPaintColor() {
        return Color.BLUE;
    }

    public int getExtraPaintGap() {
        return extraPaintGap;
    }

    public void setExtraPaintGap(int extraPaintGap) {
        this.extraPaintGap = extraPaintGap;
    }

    public TokenSequence getTokenSequence() {
        return tokenSequence;
    }

    Icon getIcon(){
        return icon;
    }
    
    public abstract String getDisplayText();

    /**
     * Returns the relative caret position.
     * The caller must call this w.r.t. the offset
     * e.g. component.setCaretPosition(offset + getCaretPosition())
     */
    public abstract int getCaretPosition();

    @Override
    public CharSequence getSortText() {
        return getItemText();
    }

    /**
     * The completion item's name.
     */
    public String getItemText() {
        return itemText;
    }

    @Override
    public void defaultAction(JTextComponent component) {
        String selectedText = component.getSelectedText();
        int charsToRemove = selectedText != null ? selectedText.length() :
                            (typedChars == null ? 0 : typedChars.length()),
            substOffset   = selectedText != null ? component.getSelectionStart() :
                            component.getCaretPosition() - charsToRemove;
        if(!shift) Completion.get().hideAll();
        if(getReplacementText().equals(typedChars))
            return;
        replaceText(component, getReplacementText(), substOffset, charsToRemove);
    }

    /**
     * Actually replaces a piece of document by passes text.
     * @param component a document source
     * @param text a string to be inserted
     * @param offset the target offset
     * @param len a length that should be removed before inserting text
     */
    protected void replaceText(final JTextComponent component, final String text,
        final int offset, final int len) {
        final BaseDocument doc = (BaseDocument) component.getDocument();
        doc.runAtomic(new Runnable() {
            @Override
            public void run() {
                try {
                    int caretPos = component.getCaretPosition();
                    if ((context != null) && (context.canReplace(text))) {
                        if (len > 0) doc.remove(offset, len);

                        String insertingText = getInsertingText(component, text);
                        doc.insertString(offset, insertingText, null);
                        // fix for issue #186007
                        caretPos = component.getCaretPosition(); // get the caret position
                    } else {
                        caretPos = offset + getCaretPosition(); // change the caret position
                    }
                    int docLength = doc.getLength();
                    if (docLength == 0) {
                        caretPos = 0;
                    } else if (caretPos > doc.getLength()) {
                        caretPos = doc.getLength();
                    }
                    component.setCaretPosition(caretPos);

                    String prefix = CompletionUtil.getPrefixFromTag(text);
                    if (prefix == null) {
                        return;
                    }
                } catch (Exception e) {
                    log.log(Level.SEVERE,
                        e.getMessage() == null ? e.getClass().getName() : e.getMessage(), e);
                }
            }
        });
    }

    protected String getInsertingText(JTextComponent component, String primaryText) {
        if ((primaryText == null) || (primaryText.length() < 1)) {
            return primaryText;
        }
        int textPos = component.getCaret().getDot();

        if (tokenSequence == null) {
            TokenHierarchy tokenHierarchy = TokenHierarchy.get(component.getDocument());
            this.tokenSequence = tokenHierarchy.tokenSequence();
        }

        tokenSequence.move(textPos);
        tokenSequence.moveNext();
        Token token = tokenSequence.token();
        boolean isTextTag = CompletionUtil.isTextTag(token);
        if ((! isTextTag) && tokenSequence.movePrevious()) {
            token = tokenSequence.token();
        }
        if (! (isTextTag || CompletionUtil.isEndTagPrefix(token) ||
            CompletionUtil.isTagFirstChar(token))) {
            return primaryText;
        }

        int tokenOffset = tokenSequence.offset();
        if (isTextTag) {
            String tokenText = token.text().toString();
            boolean isCaretAfterTag =
                (tokenText.startsWith(CompletionUtil.END_TAG_PREFIX) &&
                (textPos == tokenOffset + CompletionUtil.END_TAG_PREFIX.length()))
                ||
                (tokenText.startsWith(CompletionUtil.TAG_FIRST_CHAR) &&
                (textPos == tokenOffset + CompletionUtil.TAG_FIRST_CHAR.length()));
            if (! isCaretAfterTag) {
                return primaryText;
            }
        }

        if ((tokenOffset > -1) && (tokenOffset < textPos)) {
            textPos = tokenOffset;
        }

        boolean isDifferentTextFound = false;
        int i = 0;
        for (; i < primaryText.length(); ++i, ++textPos) {
            try {
                String strDoc  = component.getText(textPos, 1),
                       strText = primaryText.substring(i, i + 1);
                isDifferentTextFound = (! strDoc.equals(strText));
                if (isDifferentTextFound) break;
            } catch(BadLocationException e) {
                log.log(Level.WARNING,
                    e.getMessage() == null ? e.getClass().getName() : e.getMessage(), e);
                isDifferentTextFound = true;
            }
        }
        String text = isDifferentTextFound ? primaryText.substring(i) : "";
        return text;
    }

    
    @Override
    public CompletionTask createToolTipTask() {
        return new AsyncCompletionTask(new AsyncCompletionQuery() {

            @Override
            protected void query(CompletionResultSet completionResultSet, Document document, int i) {
                JToolTip toolTip = new JToolTip();
                toolTip.setTipText("Press Enter to insert \"" + getItemText() + "\"");
                completionResultSet.setToolTip(toolTip);
                completionResultSet.finish();
            }
        });
    }

    @Override
    public CompletionTask createDocumentationTask() {
        return new AsyncCompletionTask(new AsyncCompletionQuery() {
            @Override
            protected void query(CompletionResultSet completionResultSet, Document document, int i) {
                completionResultSet.setDocumentation(new AwlCompletionDocumentation1(AbstractCompletionItem1.this));
                completionResultSet.finish();
            }
        });
    }

    @Override
    public void render(Graphics g, Font defaultFont,
            Color defaultColor, Color backgroundColor,
            int width, int height, boolean selected) {
        AwlCompletionPaintComponent renderComponent = getPaintComponent();
        renderComponent.setFont(defaultFont);
        renderComponent.setForeground(defaultColor);
        renderComponent.setBackground(backgroundColor);
        renderComponent.setBounds(0, 0, width, height);
        renderComponent.setSelected(selected);
        renderComponent.paintComponent(g);
    }

    public abstract AwlCompletionPaintComponent getPaintComponent();

    @Override
    public int getPreferredWidth(Graphics g, Font defaultFont) {
        AwlCompletionPaintComponent renderComponent = getPaintComponent();
        return renderComponent.getPreferredSize().width;
    //return getPaintComponent().getWidth(getItemText(), defaultFont);
    }

}
